package com.beta.service.impl;

import com.baomidou.framework.service.impl.SuperServiceImpl;
import com.beta.constant.CommonConstant;
import com.beta.em.order.TransactionType;
import com.beta.entity.*;
import com.beta.mapper.UserBalancesMapper;
import com.beta.service.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * UserBalances 表数据服务层接口实现类
 */
@Service
public class UserBalancesService extends SuperServiceImpl<UserBalancesMapper, UserBalances> implements IUserBalancesService {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private IUsersService usersService;
    @Autowired
    private IOrderItemsService orderItemsService;
    @Autowired
    private IRefundItemService refundItemService;
    @Autowired
    private IDateTimeService dateTimeService;
    @Autowired
    private IPaymentsService paymentsService;
    @Autowired
    private IProductItemsService productItemsService;
    @Autowired
    private IOltpTxnLogsService oltpTxnLogsService;

    @Override
    public void updateBalanceOnPaySuccess(Orders order) {
        Users users = usersService.selectById(order.getUserId());
        if (Objects.isNull(users)) {
            return ;
        }
        UserBalances userBalances = getUserBalanceForUpdate(users);

        // 入账更新
        updateBalance(userBalances, order.getAmount(), users, order, TransactionType.PAYOUT, "小B账户入账");

        // 手续费扣除
        BigDecimal paymentFee = paymentsService.getPaymentFeeByOrderId(order.getId());
//        logger.info("扣除支付手续费 <== order:{}, payFee:{}", order.getId(), paymentFee);
        updateBalance(userBalances, paymentFee.negate(), users, order, TransactionType.PAY_FEE, "小B扣除支付手续费");
    }

    @Override
    public void updateBalanceOnCompleted(Orders order) {
        List<OrderItems> acceptedItems = orderItemsService.getAcceptedItems(order.getId());
        if (CollectionUtils.isNotEmpty(acceptedItems)) {
            updateBalanceWithAcceptedItems(order, acceptedItems);
        }

        List<OrderItems> refundAgreeItems = orderItemsService.getRefundAgreeItems(order.getId());
        if (CollectionUtils.isNotEmpty(refundAgreeItems)) {
            updateBalanceWithRefundAgreeItems(order, refundAgreeItems);
        }
    }

    private void updateBalanceWithAcceptedItems(Orders orders, List<OrderItems> acceptedItems) {
        if (CollectionUtils.isEmpty(acceptedItems)) {
            return;
        }

        Map<Integer, BigDecimal> dealerAmountMap = calcDealerAmount(acceptedItems, OrderItems::getPrice);
        if (MapUtils.isEmpty(dealerAmountMap)) {
            return;
        }

        BigDecimal payFee = paymentsService.getPaymentFeeByOrderId(orders.getId());
        BigDecimal orderAmount = orders.getAmount();
        BigDecimal deductPayFeeOrderAmount = orderAmount.subtract(payFee);
        Users retailer = usersService.selectById(orders.getUserId());

        // 锁住小B账户
        UserBalances retailerBalance = getUserBalanceForUpdate(retailer);

        dealerAmountMap.forEach((dealerId, dealerSellAmount) -> {

            // dealerIncome = deductPayFeeOrderAmount * dealerSellAmount / orderAmount
            // dealerPlatformFee = dealerSellAmount * dealerFeeRatio (default 0.015)
            BigDecimal dealerIncome = deductPayFeeOrderAmount.multiply(dealerSellAmount).divide(orderAmount, 2, CommonConstant.ROUNDING_MODE);

            // 小B账户金额扣除
            updateBalance(retailerBalance, dealerIncome.negate(), retailer, orders, TransactionType.COMPLETE, "小B确认收货");

            if (Objects.isNull(dealerId)) {
                // 自营
                oltpTxnLogsService.logPlatformTransaction(orders, TransactionType.PLATFORM_INCOME, dealerIncome.negate(), BigDecimal.ZERO);
            } else {
                // 贸易商
                Users dealer = usersService.selectById(dealerId);

                // 大B金额入账
                UserBalances dealerBalance = getUserBalance(dealer);
                updateBalance(dealerBalance, dealerIncome, dealer, orders, TransactionType.COMPLETE, "大B收入");

                // 大B平台手续费用扣除
                BigDecimal dealerPlatformFeeRatio = usersService.getDealerPlatformFeeRatio(dealer);
                BigDecimal dealerPlatformFee = dealerSellAmount.multiply(dealerPlatformFeeRatio).setScale(2, CommonConstant.ROUNDING_MODE);
                updateBalance(dealerBalance, dealerPlatformFee.negate(), dealer, orders, TransactionType.PLATFORM_FEE, "大B平台手续费扣除");
            }
        });
    }

    private void updateBalanceWithRefundAgreeItems(Orders orders, List<OrderItems> refundAgreeItems) {
        if (CollectionUtils.isEmpty(refundAgreeItems)) {
            return;
        }

        Map<Integer, BigDecimal> refundPayAmountMap = calcDealerAmount(refundAgreeItems, refundItemService::getRefundPayAmount);
        if (MapUtils.isEmpty(refundPayAmountMap)) {
            return;
        }

        // 锁住小B账户
        Users retailer = usersService.selectById(orders.getUserId());
        UserBalances retailerBalance = getUserBalanceForUpdate(retailer);

        refundPayAmountMap.forEach((Integer dealerId, BigDecimal refundItemPayAmount) -> {
            // 扣除小B退款应付款
            updateBalance(retailerBalance, refundItemPayAmount.negate(), retailer, orders, TransactionType.REFUND_AGREE, "小B退款");

            if (Objects.isNull(dealerId)) {
                oltpTxnLogsService.logPlatformTransaction(orders, TransactionType.REFUND_AGREE, refundItemPayAmount, BigDecimal.ZERO);
            } else {
                Users dealer = usersService.selectById(dealerId);
                UserBalances dealerBalance = getUserBalance(dealer);
                updateBalance(dealerBalance, refundItemPayAmount, dealer, orders, TransactionType.REFUND_AGREE, "大B退款违约金入账");
            }
        });
    }

    @Override
    public void updateBalanceOnSettled(Orders orders) {
        List<OrderItems> orderItems = orderItemsService.getOrderItemsByOrderId(orders.getId());
        if (CollectionUtils.isEmpty(orderItems)) {
            return ;
        }

        Set<Integer> dealerIds = orderItems.stream().map(orderItem -> {
            ProductItems productItems = productItemsService.selectById(orderItem.getProductItemId());
            return productItems.getUserId();
        }).collect(Collectors.toSet());

        if (CollectionUtils.isEmpty(dealerIds)) {
            return ;
        }

        // 锁住小B账户
        Users retailer = usersService.selectById(orders.getUserId());
        UserBalances retailerBalance = getUserBalanceForUpdate(retailer);
        // 清算小B
        settleRetailerBalance(retailer, retailerBalance, orders);

        dealerIds.forEach(dealerId -> {
            // 自营
            if (Objects.isNull(dealerId)) {
                return ;
            }

            Users dealer = usersService.selectById(dealerId);
            UserBalances dealerBalance = getUserBalanceForUpdate(dealer);
            // 清算大B
            settleDealerBalance(dealer, dealerBalance, orders);
        });
    }

    @Override
    public void executeResetUserBalance() {
        // warn: delete all uer_balance record
        deleteByMap(null);

        List<Users> allUsers = usersService.selectList(null);
        allUsers.forEach(this::createUserBalance);
    }

    private void settleRetailerBalance(Users retailer, UserBalances retailerBalance, Orders order) {
        List<OltpTxnLogs> retailerTxnLogs = oltpTxnLogsService.getUserOrderLogs(retailer.getId(), order.getId(), Arrays.asList(
                TransactionType.PAYOUT.getValue(),
                TransactionType.PAY_FEE.getValue(),
                TransactionType.REFUND_AGREE.getValue(),
                TransactionType.COMPLETE.getValue()
        ));
        if (CollectionUtils.isEmpty(retailerTxnLogs)) {
            return ;
        }
        BigDecimal amount = retailerTxnLogs.stream().map(OltpTxnLogs::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            return ;
        }
        amount.setScale(2, CommonConstant.ROUNDING_MODE);
        updateBalance(retailerBalance, amount.negate(), retailer, order, TransactionType.REFUND_SETTLED, "小B退款结算");
    }

    private void settleDealerBalance(Users dealer, UserBalances dealerBalance, Orders order) {
        List<OltpTxnLogs> dealerTxnLogs = oltpTxnLogsService.getUserOrderLogs(dealer.getId(), order.getId(), Arrays.asList(
                TransactionType.PAYOUT.getValue(),
                TransactionType.PAY_FEE.getValue(),
                TransactionType.REFUND_AGREE.getValue(),
                TransactionType.COMPLETE.getValue(),
                TransactionType.PLATFORM_FEE.getValue()
        ));
        if (CollectionUtils.isEmpty(dealerTxnLogs)) {
            return ;
        }
        BigDecimal amount = dealerTxnLogs.stream().map(OltpTxnLogs::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            return ;
        }
        updateBalance(dealerBalance, amount.negate(), dealer, order, TransactionType.SETTLED, "大B分账结算");
    }

    private Map<Integer, BigDecimal> calcDealerAmount(List<OrderItems> orderItems, Function<OrderItems, BigDecimal> amountCalculator) {
        if (CollectionUtils.isEmpty(orderItems)) {
            return Collections.emptyMap();
        }

        Map<Integer, BigDecimal> dealerAmountMap = new HashMap<>();
        for (OrderItems orderItem : orderItems) {
            ProductItems productItem = productItemsService.selectById(orderItem.getProductItemId());
            Integer dealerId = productItem.getUserId();
            BigDecimal dealerAmount = dealerAmountMap.getOrDefault(dealerId, BigDecimal.ZERO);
            dealerAmount = dealerAmount.add(amountCalculator.apply(orderItem));
            dealerAmountMap.put(dealerId, dealerAmount);
        }
        return dealerAmountMap;
    }

    private void updateBalance(UserBalances userBalance, BigDecimal balanceDelta, Users user, Orders orders, TransactionType txnType, String bizName) {
        logger.info("更新用户Balance记录 - {} <== user:{}, originalBalance:{}, balanceDelta:{}", bizName, userBalance.getUserId(), userBalance.getBalance(), balanceDelta);
        UserBalances userBalanceForUpdate = new UserBalances().setId(userBalance.getId());
        BigDecimal updatedBalance = userBalance.getBalance().add(balanceDelta).setScale(2, CommonConstant.ROUNDING_MODE);
        userBalanceForUpdate
                .setBalance(updatedBalance)
                .setUpdatedAt(dateTimeService.toDbDate());
        updateSelectiveById(userBalanceForUpdate);

        // 更新userBalance对象中的balance值
        userBalance.setBalance(updatedBalance);
        oltpTxnLogsService.logUserTransaction(orders, user, txnType, balanceDelta, updatedBalance);
    }

    private UserBalances getUserBalanceForUpdate(Users user) {
        UserBalances userBalances = selectOne(new UserBalances().setUserId(user.getId()));
        if (Objects.isNull(userBalances)) {
            userBalances = createUserBalance(user);
        }
        return baseMapper.selectForUpdate(userBalances.getId());
    }

    private UserBalances getUserBalance(Users user) {
        UserBalances userBalances = selectOne(new UserBalances().setUserId(user.getId()));
        if (Objects.isNull(userBalances)) {
            userBalances = createUserBalance(user);
        }
        return userBalances;
    }

    private UserBalances createUserBalance(Users users) {
        logger.info("创建用户Balance记录 <== user:{}", users.getId());

        Date dbDate = dateTimeService.toDbDate();
        UserBalances userBalancesForInsert = new UserBalances()
                .setUserId(users.getId())
                .setUserPhone(users.getPhone())
                .setUserType(users.getUserType())
                .setUsername(users.getUsername())
                .setBalance(BigDecimal.ZERO)
                .setCreatedAt(dbDate)
                .setTxnDate(dbDate)
                .setUpdatedAt(dbDate);
        insertSelective(userBalancesForInsert);
        return selectOne(new UserBalances().setUserId(users.getId()));
    }

}